/**
 * 
 */
package stubs;

import interfaces.IRange;

import java.util.ArrayList;
import java.util.StringTokenizer;

import massDistribution.ContinueRange;
import massDistribution.DiscreteMeasure;
import massDistribution.DiscreteRange;
import massDistribution.MetricType;
import exceptions.ValidRangeFormatException;

/**
 * @author Elisa Costante
 * 
 */
public class RangeParser {

	/**
	 * Parse the RangeString in order to obtain the list of range for a Metric
	 * Type.
	 * 
	 * 
	 * @param rangeString
	 *            : the string to parse.
	 * @param type
	 *            : the {@link MetricType} for the Metric
	 * @return a list of valid ranges for a metric.
	 */
	public static ArrayList<IRange> parseRange(String rangeString,
			MetricType type) throws ValidRangeFormatException {
		rangeString = clearString(rangeString);
		ArrayList<IRange> allRanges = null;
		if (rangeString != null && !rangeString.equals("")) {
			switch (type.getValue()) {
			case 1:
				allRanges = parseContinuousRanges(rangeString, type);
				return allRanges;
			case 2:
				allRanges = parseContinuousRanges(rangeString, type);
				return allRanges;
			case 3:
				allRanges = parseDiscreteRanges(rangeString);
				return allRanges;
			default:
				return null;
			}
		} else {
			allRanges = setDefaultValue(type);
			return allRanges;
		}
	}

	/**
	 * Will be assumed the range (-INF,+INF) for the continuous types while will
	 * be assumed an empty range {} for the discrete type.
	 * 
	 * @return the default ranges or null if one error was occurred.
	 */
	public static ArrayList<IRange> setDefaultValue(MetricType type) {

		// Se il campo ValidRanges e' vuoto allora il range di Default e'
		// pari
		// a (-INF,+INF) per i continui, mentre e' pari a null per i tipi
		// Discreti
		ArrayList<IRange> allRanges = new ArrayList<IRange>(1);
		switch (type.getValue()) {
		case 1:
			// Integer

			Integer lowerBoundInt = Integer.MIN_VALUE;
			Integer upperBoundInt = Integer.MAX_VALUE;
			allRanges.add(new ContinueRange(lowerBoundInt, upperBoundInt, true,
					true));
			break;
		case 2:
			// Real
			Double lowerBoundReal = Double.MIN_VALUE;
			Double upperBoundReal = Double.MAX_VALUE;
			allRanges.add(new ContinueRange(lowerBoundReal, upperBoundReal,
					true, true));
			break;
		case 3:
			// Discrete
			allRanges.add(new DiscreteRange());

			break;

		default:
			break;
		}

		return allRanges;
	}

	/**
	 * Return the list of ranges or null if the string range is not valid.
	 * 
	 * @param rangeString
	 * @return
	 * @throws ValidRangeFormatException
	 */
	private static ArrayList<IRange> parseContinuousRanges(String rangeString,
			MetricType type) throws ValidRangeFormatException {
		if (isValidContinuousString(rangeString)) {
			ArrayList<IRange> allRanges = new ArrayList<IRange>();
			// The format of String is :
			// (lowerBound-upperBound];[lowerBound-upperBound]

			StringTokenizer tokenizer = new StringTokenizer(rangeString, ";");

			while (tokenizer.hasMoreElements()) {
				// A range can be (a,b)
				String aRange = (String) tokenizer.nextElement();
				// Verify if the range is open or close
				boolean openedLeft = isOpened(aRange, "LEFT");
				boolean openedRight = isOpened(aRange, "RIGHT");
				// Replace the boundaries
				aRange = aRange.replaceAll("\\(", "");
				aRange = aRange.replaceAll("\\)", "");
				aRange = aRange.replaceAll("\\[", "");
				aRange = aRange.replaceAll("\\]", "");
				StringTokenizer rangeTokenizer = new StringTokenizer(aRange,
						",");

				ContinueRange continueRange = setContinueRange(rangeTokenizer,
						type, openedLeft, openedRight);
				// continueRange.setOpenedLeft(openedLeft);
				// continueRange.setOpenedRight(openedRight);
				allRanges.add(continueRange);

				return allRanges;
			}
		}
		return null;
	}

	/**
	 * @param rangeString
	 * @return
	 * @throws ValidRangeFormatException
	 */
	private static ArrayList<IRange> parseDiscreteRanges(String rangeString)
			throws ValidRangeFormatException {
		if (isValidDiscreteString(rangeString)) {
			// Remove the braces at the beginning and at the end
			rangeString = rangeString.replaceAll("\\{", "");
			rangeString = rangeString.replaceAll("\\}", "");
			ArrayList<IRange> allRanges = new ArrayList<IRange>();

			StringTokenizer tokenizer = new StringTokenizer(rangeString, ";");

			// The format of String must be:
			// value1;value2;value3;
			DiscreteRange discreteRange = new DiscreteRange();

			while (tokenizer.hasMoreElements()) {
				String aRange = (String) tokenizer.nextElement();
				// Discrete Measure
				DiscreteMeasure measure = new DiscreteMeasure(aRange);
				discreteRange.addElement(measure);
			}
			allRanges.add(discreteRange);

			return allRanges;

		}
		return null;
	}

	/**
	 * @param rangeTokenizer
	 * @param type
	 * @param openedRight
	 * @param openedLeft
	 * @return
	 * @throws ValidRangeFormatException
	 */
	private static ContinueRange setContinueRange(
			StringTokenizer rangeTokenizer, MetricType type,
			boolean openedLeft, boolean openedRight)
			throws ValidRangeFormatException {

		switch (type.getValue()) {
		case 1:
			// Integer
			try {
				String lb = rangeTokenizer.nextToken();
				String ub = rangeTokenizer.nextToken();
				Integer lowerBoundInt;
				Integer upperBoundInt;
				try {
					lowerBoundInt = Integer.parseInt(lb);

				} catch (NumberFormatException e) {
					if (lb.equalsIgnoreCase("-INF")) {
						// Lowerbound= -inf
						// Opened left = false
						lowerBoundInt = Integer.MIN_VALUE;
						openedLeft = false;
					} else
						throw new ValidRangeFormatException(
								"the lowerBound is not valid");
				}

				try {
					upperBoundInt = Integer.parseInt(ub);
				} catch (NumberFormatException e) {
					if (ub.equalsIgnoreCase("+INF")) {
						// Lowerbound= +inf
						// openendRight=false
						upperBoundInt = Integer.MAX_VALUE;
						openedRight = false;
					} else
						throw new ValidRangeFormatException(
								"the upperBound is not valid");

				}

				if (lowerBoundInt > upperBoundInt)
					throw new ValidRangeFormatException(
							"Constraint lowerBound<upperBound not Respected");
				return new ContinueRange(lowerBoundInt, upperBoundInt,
						openedLeft, openedRight);
			} catch (Exception e) {
				throw new ValidRangeFormatException(
						"Missmatching Exception: the type of range is not valid for the value.");
			}

		case 2:
			// Real
			String lb = rangeTokenizer.nextToken();
			String ub = rangeTokenizer.nextToken();
			Double lowerBoundReal;
			Double upperBoundReal;
			try {
				lowerBoundReal = Double.parseDouble(lb);

			} catch (NumberFormatException e) {
				if (lb.equalsIgnoreCase("-INF")) {
					// Lowerbound= -inf
					lowerBoundReal = Double.MIN_VALUE;
					openedLeft = false;
				} else
					throw new ValidRangeFormatException(
							"the lowerBound is not valid");
			}

			try {
				upperBoundReal = Double.parseDouble(ub);
			} catch (NumberFormatException e) {
				if (ub.equalsIgnoreCase("+INF")) {
					// Lowerbound= +inf
					upperBoundReal = Double.MAX_VALUE;
					openedRight = false;
				} else
					throw new ValidRangeFormatException(
							"the upperBound is not valid");

			}
			if (lowerBoundReal > upperBoundReal)
				throw new ValidRangeFormatException(
						"Constraint lowerBound<upperBound not Respected");
			return new ContinueRange(lowerBoundReal, upperBoundReal,
					openedLeft, openedRight);

		default:
			break;
		}

		return null;
	}

	/**
	 * Return true if the left/right brace is "(" or ")", false if they are
	 * equals to "[" or "]".
	 * 
	 * @param aRange
	 * @param string
	 * @return
	 */
	private static boolean isOpened(String aRange, String LeftRight) {
		if (LeftRight.equalsIgnoreCase("left")) {
			// Querying for the leftside
			if (aRange.startsWith("("))
				return true;
			else
				return false;
		} else if (LeftRight.equalsIgnoreCase("right")) {
			// Querying for the right side
			if (aRange.endsWith(")"))
				return true;
			else
				return false;
		}
		return false;
	}

	/**
	 * @return
	 */
	private static boolean isValidContinuousString(String rangeString) {
		if (rangeString.startsWith("(") || rangeString.startsWith("[")
				|| rangeString.endsWith(")") || rangeString.startsWith("]"))
			return true;
		return false;
	}

	/**
	 * Return true if the string is valid for the discrete type that means it
	 * starts and ends with braces
	 * 
	 * @param rangeString
	 * @return
	 */
	private static boolean isValidDiscreteString(String rangeString) {
		if (rangeString.startsWith("{") && rangeString.endsWith("}"))
			return true;
		return false;
	}

	/**
	 * Remove all the white space within the <code>rangeString</code>
	 * 
	 * @param rangeString
	 *            : the {@link String} to clear.
	 * @return a string without white space.
	 */
	private static String clearString(String rangeString) {
		rangeString = rangeString.replace(" ", "");
		return rangeString;
	}

}
